前言
Java 9带来了很多新的增强,这些增强将在很大程度上影响我们的编程风格和习惯。最大的变化是Java的模块化。这是Java 8中的Lambdas之后的另一个重大变化。
本文中,我们将介绍JDK 9的新特性及增强。
正文
模块化(Java platform module system)
JPMS (Java平台模块系统)是新版Java 9的核心亮点。它也被称为“Jigshaw计划”。模块是新的结构,就像我们已经有了包一样。使用新的模块化编程开发的应用程序可以看作是具有定义良好的边界和这些模块之间的依赖关系的交互模块的集合。
JPMS包括对编写模块化应用程序的支持,以及对JDK源代码的模块化。JDK 9附带了大约92个模块(在GA版本中可以进行更改)。Java 9模块系统有一个“Java”。它被称为基模块。它是一个独立的模块,不依赖于任何其他模块。默认情况下,所有其他模块都依赖于“java.base”。
java模块化编程要点:
- 模块通常只是一个jar文件,在根目录下有一个module-info.class文件。
- 要使用模块,请将jar文件包含到modulepath中,而不是classpath中。添加到classpath中的模块jar文件是普通的jar文件,而module-info.class文件将被忽略。
典型的module-info.java类是这样的:
1 | module helloworld { |
关于模块化的更多介绍可以查看 Java 9模块化
接口私有方法(Interface Private Methods)
Java 8允许在接口中编写默认方法,这是一个广受好评的特性。因此,在这之后,接口中缺少的只有私有方法了。从Java 9开始,我们可以在接口中声明私有方法。
这些私有方法将提高接口内部的代码可重用性。例如,如果两个默认方法需要共享代码,一个私有接口方法将允许它们这样做,但是不会将这个私有方法暴露给它的实现类。
在接口中使用私有方法有四个规则:
- 私有接口方法不能是抽象的。
- 私有方法只能在接口内部使用。
- 私有静态方法可以在其他静态和非静态接口方法中使用。
- 私有非静态方法不能在私有静态方法中使用。
一个使用私有方法接口的例子:
1 | public interface CustomCalculator |
关于接口私有方法的更多内容可以查看 Java 9接口私有方法
HTTP/2 Client
HTTP/1.1客户端在1997年发布。从那以后发生了很大的变化。因此,Java 9引入了一个新的API,它使用起来更干净、更清晰,并且还增加了对HTTP/2的支持。
新的API使用了3个主要的类:HttpClient
, HttpRequest
和HttpResponse
。
要发出请求,只需获取客户端、构建请求并发送请求,如下所示。
1 | HttpClient httpClient = HttpClient.newHttpClient(); |
上面的代码看起来更清晰可读了。
新的API还支持使用httpClient.sendAsync()
方法的异步HTTP请求。它返回CompletableFuture
对象,该对象可用于确定请求是否已完成。它还提供了在请求完成后对HttpResponse
的访问。最好的一点是,如果你想,你甚至可以在请求完成之前取消它。
我们来看一个例子:
1 | if(httpResponse.isDone()) { |
JShell 工具(JShell – REPL Tool)
JShell是JDK 9发行版附带的新的命令行交互工具[JEP 222],用于评估用Java编写的声明、语句和表达式。JShell允许我们执行Java代码片段并立即获得结果,而不必创建解决方案或项目。
Jshell很像linux操作系统中的命令窗口。不同之处在于JShell是特定于Java的。除了执行简单的代码片段外,它还有许多其他功能。如:
- 在单独的窗口中启动内建代码编辑器
- 在单独的窗口中启动您选择的代码编辑器
- 当保存操作在这些外部编辑器中发生时执行代码
- 从文件系统加载预先编写的类
关于JShell的更多内容,请查看 Java 9 JShell。
平台和JVM日志(Platform and JVM Logging)
JDK 9通过一个新的loging API改进了平台类(JDK类)和JVM组件的日志记录。它允许我们指定自己选择的日志记录框架(例如Log4J2)作为来自JDK类的日志记录消息的日志记录后端。
关于这个API,我们需要知道以下几点:
- API应该由JDK中的类使用,而不是由应用程序类使用。
- 对于我们的应用程序代码,我们将像以前一样继续使用其他日志api。
- 该API不允许我们以编程方式配置日志记录器。
API包含以下内容:
- 一个服务接口,
java.lang.System.LoggerFinder
,它是一个抽象的静态类 - 一个接口
java.lang.System.Logger
,它提供了日志API - 一个重载方法
getLogger()
位于java.lang.System
类,它返回一个logger实例。
JDK 9还添加了一个新的命令行选项-Xlog
,它为我们提供了一个访问所有JVM类中记录的所有消息的单点访问点。
下面是使用-Xlog
选项的语法:
1 | -Xlog [:][:[][:[][:]]] |
所有选项都是可选的。如果缺少-Xlog
中的前一部分,则必须为该部分使用冒号。
例如,-Xlog::stderr
表示所有部件都是默认的,其中输出设置为stderr。
关于JVM Logging的更多内容,我们后面讨论。
进程API更新(Process API Updates)
在Java 5之前,生成新进程的惟一方法是使用Runtime.getRuntime().exec()方法。然后在Java 5中引入了ProcessBuilder API,它支持一种更干净的生成新进程的方式。现在Java 9增加了一种获取当前进程和任何衍生进程信息的新方法。
要获取任何进程的信息,现在应该使用java.lang.ProcessHandle.Info
接口。这个接口在获取进程较多信息时很有用。如以下进程信息:
- 用于启动进程的命令
- 命令的参数
- 启动过程的时间瞬间
- 它和创建它的用户所花费的总时间
1 | ProcessHandle processHandle = ProcessHandle.current(); |
要获取新派生进程的信息,请使用process.toHandle()
方法获取ProcessHandle
实例。剩下的事情如上所述。
1 | String javaPrompt = ProcessUtils.getJavaCmd().getAbsolutePath(); |
还可以使用ProcessHandle.allProcesses()
获取系统中所有可用进程的ProcessHandle
流。
要获得所有子进程的列表(一级的和n级深度的),可以使用children()
和 descendants()
方法。
1 | Stream<ProcessHandle> children = ProcessHandle.current().children(); |
集合API更新(Collection API Updates)
从Java 9开始,我们可以使用新的工厂方法创建不可变集合,如不可变list、不可变set和不可变map。如下:
1 | import java.util.List; |
关于更多的集合API更新内容请查看 Java 9集合相关更新
Stream API相关更新(Stream API Improvements)
Java 9引入了两种与流交互的新方法,即takeWhile
/dropWhile
方法。此外,它还添加了两个重载方法,即ofNullable
和iterate
方法。
新的方法takeWhile
和dropWhile
允许我们根据谓词获取流的一部分。
- 在一个有序的流上,
takeWhile
返回从流中取出的与给定谓词匹配的元素的“最长前缀”,从流的开头开始。dropWhile
返回未被takeWhile
匹配的剩余项。 - 在一个无序流上,
takeWhile
从流的开头开始返回匹配给定谓词(但不是全部)的流元素的子集。dropWhile
在删除匹配给定谓词的元素子集后返回剩余的流元素。
类似地,在Java 8之前,流中不能有null
值。它会导致NullPointerException
。自Java 9以来,Stream.ofnullable()
方法允许我们创建一个单元素流,它包装一个值(如果不是null
),或者是一个空流。从技术上讲,Stream.ofnullable()
与在流API上下文中使用null
条件检查非常相似。
关于Stream API更新更多内容,可以查看 Java 9 Stream API更新
多版本JAR引用增强(Multi-Release JAR Files)
这个增强与我们如何在jar文件中打包应用程序类有关。在此之前,我们必须将所有类打包到一个jar文件中,并放入另一个希望使用它的应用程序的类路径中。
使用多版本特性,现在jar可以包含一个类的不同版本——兼容于不同的JDK版本。类的不同版本的信息,以及在哪个JDK版本中哪个类应该被类加载,都存储在MANIFEST.MF
文件中。在本例中,MANIFEST.MF
文件主要部分包含了条目Multi-Release: true
。
而且,META-INF
包含一个版本子目录,它的整数命名的子目录(从9开始(对于Java 9))存储特定于版本的类和资源文件。如下:
1 | JAR content root |
让我们假设在JDK 10中,A.class被更新以利用一些Java 10特性,然后这个Jar文件可以这样更新:
1 | JAR content root |
这看起来是一个很有用的增强,它解决了在大型应用程序中经常出现的依赖问题,在这些应用程序中,具有不同版本的jar彼此不兼容。这个特性对解决这些场景有很大的帮助。
@Deprecated标签(@Deprecated Tag Changes)
从Java 9开始,@Deprecated
注释将有两个属性,即forRemoval
和since
。
forRemoval
:指示所注释的元素在将来的版本中是否要被删除。since
:它返回注释元素被弃用的版本。
强烈建议在文档中使用@deprecated
javadoc标记解释弃用该API的原因。文档还应该建议并链接到推荐的替换API(如果适用的话)。替换API通常有一些与原API不同的地方,因此也应该作出说明。
堆栈相关(Stack Walking)
堆栈是后进先出(LIFO)数据结构。在JVM级别,堆栈存储帧。每次调用一个方法时,都会创建一个新帧并将其推到堆栈的顶部。当方法调用完成时,帧将被销毁(从堆栈中弹出)。堆栈上的每个帧都包含自己的局部变量数组,以及自己的操作数堆栈、返回值和对当前方法类的运行时常量池的引用。
在给定的线程中,在任何点上都只有一个帧是活动的。活动帧称为当前帧,其方法称为当前方法。(了解更多)
在Java 8之前,StackTraceElement
表示一个堆栈帧。要获得完整的堆栈,必须使用Thread.getStackTrace()
和Throwable.getStackTrace()
。它返回一个StackTraceElement
数组,我们可以迭代该数组以获得所需的信息。
在Java 9中,引入了一个新的类StackWalker
。该类使用当前线程的连续堆栈帧流提供了简单而有效的堆栈遍历。StackWalker
类非常高效,因为它对堆栈帧的计算是延迟的。
1 | //打印当前线程的所有堆栈帧的详细信息 |
我们还可以用这个流做很多其他的事情,我们将在其他的文章中讨论。
Java Docs 相关更新(Java Docs Updates)
Java 9增强了javadoc
工具来生成HTML5标记。它当前在HTML 4.01中生成页面。
为了生成HTML5 Javadoc,需要在命令行参数中加入参数-html5
。要在命令行生成文档,可以运行以下命令:
1 | javadoc [options] [packagenames] [sourcefile] [@files] |
使用HTML5带来了更简单的HTML5结构的好处。它还实现了WAI-ARIA standard 可访问性标准。这样做的目的是让有生理或视觉缺陷的人更容易使用屏幕阅读器之类的工具访问javadocs
页面。
JEP 225允许在javadoc
中搜索程序元素和带标记的单词和短语。
以下将被索引和搜索:
- 模块的声明名称(Declared names of modules)
- 包(Packages)
- 类型和成员(Types and members)
- 方法参数类型的简单名称(The simple name of method parameter types)
这是在客户端实现的,带有一个新的search.js
Javascript文件,以及在生成javadoc
时生成的索引。在生成的HTML5 API页面上有一个搜索框可用。
请注意,搜索选项将默认添加,但可以关闭参数:-noindex
。
其它特性及增强(Miscellaneous Other Features)
在Java 9中还有其他特性,我把它们列在下面以供快速参考。我们将在以后的文章中讨论所有这些特性。
- Reactive Streams API
- GC改进(GC (Garbage Collector) Improvements)
- Filter Incoming Serialization Data
- 弃用Applet(Deprecate the Applet API)
- Indify String Concatenation
- Enhanced Method Handles
- Compact Strings
- Nashorn解析器(Parser API for Nashorn)